﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Compilex.Automata;
using Infraestruturex.Automata.SPA;
using Compilex.BaseLibrary;
using Infraestruturex.BaseLibrary;

namespace Infraestruturex.Automata.SPA
{
    [Serializable]
    public class SPA : IAutomaton<SPAState>
    {
        public string Name { get; set; }
        public SPAState StartState { get; set; }

        [NonSerialized]
        private IBasicTokenProvider tokenProvider;
        public IBasicTokenProvider TokenProvider
        {
            get { return tokenProvider; }
            set { tokenProvider = value; }
        }
        
        public SemanticActionManager SemanticActionManager { get; set; }

        public SPA(string name)
        {
            this.Name = name;
            this.SemanticActionManager = new SemanticActionManager();
            this.TokenProvider = new SequentialCharsTokenProvider();
        }

        public override string ToString()
        {
            return Name;
        }

        public bool Accepts(string input)
        {
            TokenProvider.Input = input;
            return NextState(StartState, true);
        }

        public bool Accepts()
        {
            return NextState(StartState, true);
        }

        public SPAState[] GetStates()
        {
            HashSet<SPAState> states = new HashSet<SPAState>();
            states.Add(this.StartState);
            int lastStateCount;
            do
            {
                lastStateCount = states.Count;
                HashSet<SPAState> newStates = new HashSet<SPAState>();
                foreach (SPAState state in states)
                {
                    if (state.IsSubmachineCallState)
                    {
                        newStates.Add(state.SubmachineCallReturnState);
                    }
                    for (int i = 0; i < state.Transitions.Count; i++)
                    {
                        newStates.Add(state.Transitions[i].Destination);
                    }
                }
                states.UnionWith(newStates);
            } while (states.Count > lastStateCount);
            return states.ToArray();
        }

        private bool Call()
        {
            return NextState(StartState, false);
        }

        private bool NextState(SPAState state, bool isRootSubmachine)
        {
            if (state == null)
            {
                return false;
            }
            if (TokenProvider.HasMoreTokens())
            {
                // se tiver a escolha entre realizar uma chamada de submáquina
                // e consumir um token, o SPA escolhe consumir
                string nextToken = TokenProvider.PeekNextTokenClass();
                SPAState nextState = state.TryGetDestinationState(nextToken);
                if (nextState != null)
                {
                    SemanticActionManager.RunSemanticAction(state.TryGetSemanticActionName(nextToken));
                    TokenProvider.ConsumeNextToken();
                    return NextState(nextState, isRootSubmachine);
                }
                else if (state.IsSubmachineCallState)
                {
                    SPA submachine = state.SubmachineToCall;
                    submachine.TokenProvider = this.TokenProvider;
                    submachine.SemanticActionManager = this.SemanticActionManager;

                    if (submachine.Call())
                    {
                        SemanticActionManager.RunSemanticAction(state.SubmachineCallSemanticActionName);
                        return NextState(state.SubmachineCallReturnState, isRootSubmachine);
                    }
                    else
                        if (!isRootSubmachine)
                            return state.IsAcceptState;
                        else
                            return false;
                }
                else
                {
                    if (!isRootSubmachine)
                        return state.IsAcceptState;
                    else
                        return false;
                }
            }
            else
                return state.IsAcceptState;
        }

        public void OrganizeStateNames()
        {
            const string prefix = "q";
            int number = 0;
            SPAState[] states = GetStates();
            foreach (SPAState state in states)
            {
                state.Name = prefix + number;
                number++;
            }
        }

        public string DumpString()
        {
            StringBuilder blder = new StringBuilder();
            SPAState[] states = GetStates();
            blder.AppendLine(String.Format("Submáquina {0} ({1} estados)", Name, states.Length));

            foreach(SPAState state in states)
            {
                blder.AppendLine(String.Format("{0} {1}", state.Name,
                    state.IsAcceptState ? "(aceitação)" : ""));
                if(state.IsSubmachineCallState)
                {
                    blder.AppendLine(
                        String.Format("\t{0} =={1}/{2}==> {3}",
                        state.Name, state.SubmachineToCall.Name,
                        state.SubmachineCallSemanticActionName,
                        state.SubmachineCallReturnState.Name));
                }
                foreach(var transition in state.Transitions.ToArray())
                {
                    blder.AppendLine(String.Format("\t{0} -\"{1}\"/{2}-> {3}",
                        state.Name, transition.Input, transition.SemanticActionName,
                        transition.Destination.Name));
                }
                blder.AppendLine();
            }
            
            return blder.ToString();
        }
    }
}
